package de.sordul.md5crack;

import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.math.BigInteger;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import org.ardverk.concurrent.AsyncExecutorService;
import org.ardverk.concurrent.AsyncExecutors;
import org.ardverk.concurrent.AsyncFuture;
import org.ardverk.concurrent.AsyncFutureListener;

import de.sordul.md5crack.impl.optimizedMultiCore.MCOptimizedMD5Crack;
import de.sordul.md5crack.util.Charset;

public class CrackGUI {

	private static MCOptimizedMD5Crack md5Crack = null;
	private JFrame frmMdcrack;
	private JTextField inputMD5;
	private JComboBox<String> inputMaxLength;
	private JComboBox<String> inputCharset;
	private static JTextField txtComputedHashes;
	private static String result = null;
	private JProgressBar progressBar;
	private JButton btnStart;
	private static JLabel lblElapsedTime;
	private AsyncExecutorService executor;
	private boolean isComputing = false;
	private JTextField txtResult;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					CrackGUI window = new CrackGUI();
					window.frmMdcrack.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public CrackGUI() {
		initialize();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
			e.printStackTrace();
		}

		frmMdcrack = new JFrame();
		
		frmMdcrack.setIconImage(new ImageIcon(getClass().getResource("/icon.png")).getImage());
		frmMdcrack.setResizable(false);
		frmMdcrack.setTitle("MD5Crack");
		frmMdcrack.setBounds(100, 100, 403, 300);
		frmMdcrack.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frmMdcrack.getContentPane().setLayout(null);
		
		inputMD5 = new JTextField();
		inputMD5.setFont(new Font("DejaVu Sans Mono", Font.PLAIN, 11));
		inputMD5.setBounds(44, 11, 340, 20);
		frmMdcrack.getContentPane().add(inputMD5);
		inputMD5.setColumns(10);
		
		JLabel lblMd = new JLabel("MD5");
		lblMd.setBounds(10, 14, 46, 14);
		frmMdcrack.getContentPane().add(lblMd);
		
		JSeparator separator = new JSeparator();
		separator.setBounds(10, 44, 375, 2);
		frmMdcrack.getContentPane().add(separator);
		
		JLabel lblSettings = new JLabel("Settings");
		lblSettings.setFont(new Font("Tahoma", Font.BOLD, 11));
		lblSettings.setBounds(10, 57, 56, 14);
		frmMdcrack.getContentPane().add(lblSettings);
		
		JLabel lblMaximumLengthTo = new JLabel("Maximum length to search for");
		lblMaximumLengthTo.setBounds(20, 82, 142, 14);
		frmMdcrack.getContentPane().add(lblMaximumLengthTo);
		
		inputMaxLength = new JComboBox<String>();
		inputMaxLength.setModel(new DefaultComboBoxModel<String>(new String[] {"Unlimited", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}));
		inputMaxLength.setBounds(172, 79, 117, 20);
		frmMdcrack.getContentPane().add(inputMaxLength);
		
		JLabel lblApplicableCharset = new JLabel("Applicable charset");
		lblApplicableCharset.setBounds(20, 113, 142, 14);
		frmMdcrack.getContentPane().add(lblApplicableCharset);
		
		inputCharset = new JComboBox<String>();
		inputCharset.setModel(new DefaultComboBoxModel<String>(new String[] {"Latin (ASCII)", "Extended Latin", "Full unicode"}));
		inputCharset.setBounds(172, 110, 117, 20);
		frmMdcrack.getContentPane().add(inputCharset);
		
		btnStart = new JButton("Start");
		btnStart.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if(validMD5()) {
					
					if(!isComputing) {
						btnStart.setText("Stop");
						startComputation();
					} else {
						resetCracker();
					}
				}
				

				
				else {
					JOptionPane.showMessageDialog(frmMdcrack.getContentPane(),
						    "The entered value is not a valid MD5.",
						    "Error",
						    JOptionPane.ERROR_MESSAGE);
				}
			}
		});
		btnStart.setBounds(299, 78, 89, 55);
		frmMdcrack.getContentPane().add(btnStart);
		
		JSeparator separator_1 = new JSeparator();
		separator_1.setBounds(10, 147, 375, 2);
		frmMdcrack.getContentPane().add(separator_1);
		
		JLabel lblLog = new JLabel("Progress");
		lblLog.setFont(new Font("Tahoma", Font.BOLD, 11));
		lblLog.setBounds(10, 160, 56, 14);
		frmMdcrack.getContentPane().add(lblLog);
		
		progressBar = new JProgressBar();
		progressBar.setBounds(20, 180, 367, 14);

		frmMdcrack.getContentPane().add(progressBar);
		
		JLabel lblComputedHashed = new JLabel("Computed hashes");
		lblComputedHashed.setBounds(20, 205, 142, 14);
		frmMdcrack.getContentPane().add(lblComputedHashed);
		
		txtComputedHashes = new JTextField();
		txtComputedHashes.setEditable(false);
		txtComputedHashes.setBounds(172, 202, 117, 20);
		frmMdcrack.getContentPane().add(txtComputedHashes);
		txtComputedHashes.setColumns(10);
		
		JLabel lblResultText = new JLabel("Result");
		lblResultText.setBounds(20, 247, 46, 14);
		frmMdcrack.getContentPane().add(lblResultText);
		
		JLabel lblElapsedTimeText = new JLabel("Elapsed time");
		lblElapsedTimeText.setBounds(20, 222, 142, 14);
		frmMdcrack.getContentPane().add(lblElapsedTimeText);
		
		lblElapsedTime = new JLabel("");
		lblElapsedTime.setBounds(172, 222, 212, 14);
		frmMdcrack.getContentPane().add(lblElapsedTime);
		
		txtResult = new JTextField();
		txtResult.setEditable(false);
		txtResult.setBounds(172, 244, 212, 20);
		frmMdcrack.getContentPane().add(txtResult);
		txtResult.setColumns(10);
		txtResult.setBorder( BorderFactory.createEmptyBorder() );
	}
	
	/**
	 * Checks for a valid MD5
	 * @return TRUE for valid MD5, FALSE otherwise
	 */
	protected boolean validMD5() {
		String pattern= "([0-9a-f]){32}";
		String input = inputMD5.getText();
		if(input.matches(pattern)) {
			return true;
		}
		return false;
	}

	/**
	 * Reset the Cracker to be prepared for a new MD5
	 */
	protected void resetCracker() {
		md5Crack.stop();
		
		inputMaxLength.setSelectedIndex(0);
		inputCharset.setSelectedIndex(0);
		txtResult.setText("");
		progressBar.setIndeterminate(false);
		progressBar.setValue(0);
		lblElapsedTime.setText("");
		btnStart.setText("Start");
		txtComputedHashes.setText("");
		
		isComputing = false;
		md5Crack = null;
	}

	private void startComputation() {
		md5Crack = new MCOptimizedMD5Crack();
		md5Crack.setMd5( inputMD5.getText() );
		md5Crack.setLength( inputMaxLength.getSelectedIndex() );
		md5Crack.setCharset( getCharsetByString(inputCharset.getSelectedIndex()) );
		
		progressBar.setIndeterminate(true);
		
		// Create an executor and start md5Crack
		executor = AsyncExecutors.newFixedThreadPool(1);
		Callable<String> task = md5Crack;
		AsyncFuture<String> future = executor.submit(task);
		isComputing = true;
		executor.shutdown();		
		
		// Callback for the crack result
		future.addAsyncFutureListener(new AsyncFutureListener<String>() {
			
	        @Override
	        public void operationComplete(AsyncFuture<String> future) {
	        	try {
	        		result = future.get();
	        		txtResult.setText(result);
					progressBar.setIndeterminate(false);
					progressBar.setValue(100);
					isComputing = false;
					btnStart.setText("Start");
					md5Crack = null;
				} catch (InterruptedException | ExecutionException e) {
					e.printStackTrace();
				}
	        }
	    });
	}

	/**
	 * Determines the desired charset by given index.
	 * @param index Combobox index
	 * @return Determined Charset
	 */
	private Charset getCharsetByString(int index) {
		switch(index) {
			case 0:
				return Charset.LATIN;
			case 1:
				return Charset.LATIN_EXT;
			case 2:
				return Charset.FULL;
			default:
				return Charset.LATIN;
		}
	}

	/**
	 * Set the number of computed hashes
	 * @param computedHashesSum Number of computed hashes
	 */
	public static void setComputedHashes(BigInteger computedHashesSum) {
		txtComputedHashes.setText(String.valueOf(computedHashesSum));
	}

	/**
	 * Return the computation result
	 * @return Result string
	 */
	public static String getResult() {
		return result;
	}

	public static void setLblElapsedTime(String text) {
		lblElapsedTime.setText(text);
	}
}
